namespace massagepack {
    export class Extended {
        type;
        data;
        constructor(type, data) {
            if ((type < -256) || (type > 255)) {
                throw RangeError("mpack: invalid extended type [" + type + "]")
            }
            this.type = type;
            this.data = data;
        }
    }

    export class Decoder {
        buffer;
        offset;
        view;
        private NIL = 0xc0;
        private FALSE = 0xc2;
        private TRUE = 0xc3;
        private BIN8 = 0xc4;
        private BIN16 = 0xc5;
        private BIN32 = 0xc6;
        private EXT8 = 0xc7;
        private EXT16 = 0xc8;
        private EXT32 = 0xc9;
        private FLOAT32 = 0xca;
        private FLOAT64 = 0xcb;
        private UINT8 = 0xcc;
        private UINT16 = 0xcd;
        private UINT32 = 0xce;
        private UINT64 = 0xcf;
        private INT8 = 0xd0;
        private INT16 = 0xd1;
        private INT32 = 0xd2;
        private INT64 = 0xd3;
        private FIXEXT1 = 0xd4;
        private FIXEXT2 = 0xd5;
        private FIXEXT4 = 0xd6;
        private FIXEXT8 = 0xd7;
        private FIXEXT16 = 0xd8;
        private STR8 = 0xd9;
        private STR16 = 0xda;
        private STR32 = 0xdb;
        private ARRAY16 = 0xdc;
        private ARRAY32 = 0xdd;
        private MAP16 = 0xde;
        private MAP32 = 0xdf;
        private FIXARRAY = 0x90;
        private FIXSTR = 0xa0;
        private FIXMAP = 0x80;
        private POSITIVE_FIXNUM = 0x00;
        private NEGATIVE_FIXNUM = 0xe0;
        constructor(buffer) {
            if (buffer instanceof ArrayBuffer) {
                this.buffer = buffer
                this.offset = 0
                this.view = new DataView(this.buffer)
            }
            else { // expects a TypedArray
                this.buffer = buffer.buffer
                this.offset = 0
                this.view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
            }
        }

        static toArrayBuffer(buf) {
            var ab = new ArrayBuffer(buf.length);
            var view = new Uint8Array(ab);
            for (var i = 0; i < buf.length; ++i) {
                view[i] = buf[i];
            }
            return ab;
        }

        decode() {
            var offset = this.offset
            try {
                return this.decodeObject(this);
            }
            catch (e) {
                this.offset = offset

                if (!(e instanceof RangeError)) {
                    throw e
                }
            }
        }

        decodeUTF8(s) {
            return utf8.decode(s)
        }

        decodeStringOfLength(self, length) {
            var off = self.offset
            var buf = undefined
            var str = undefined
            var i = undefined
            var n = undefined

            self.offset += length
            buf = new Uint8Array(self.view.buffer, self.view.byteOffset + off, length)
            str = ''

            for (i = 0, n = buf.byteLength; i !== n; ++i) {
                str += String.fromCharCode(buf[i])
            }

            return this.decodeUTF8(str)
        }

        decodeFixstr(self, tag) {
            return this.decodeStringOfLength(self, tag & ~this.FIXSTR)
        }

        decodeStr8(self) {
            return this.decodeStringOfLength(self, this.decodeUint8(self))
        }

        decodeStr16(self) {
            return this.decodeStringOfLength(self, this.decodeUint16(self))
        }

        decodeStr32(self) {
            return this.decodeStringOfLength(self, this.decodeUint32(self))
        }

        decodeBin8(self) {
            var length = this.decodeUint8(self)
            var offset = self.offset
            self.offset += length
            return new Uint8Array(self.view.buffer, self.view.byteOffset + offset, length)
        }

        decodeBin16(self) {
            var length = this.decodeUint16(self)
            var offset = self.offset
            self.offset += length
            return new Uint8Array(self.view.buffer, self.view.byteOffset + offset, length)
        }

        decodeBin32(self) {
            var length = this.decodeUint32(self)
            var offset = self.offset
            self.offset += length
            return new Uint8Array(self.view.buffer, self.view.byteOffset + offset, length)
        }

        decodePositiveFixnum(self, tag) {
            return tag
        }

        decodeNegativeFixnum(self, tag) {
            return self.view.getInt8(self.offset - 1)
        }

        decodeUint8(self) {
            var value = self.view.getUint8(self.offset)
            self.offset += 1
            return value
        }

        decodeUint16(self) {
            var value = self.view.getUint16(self.offset)
            self.offset += 2
            return value
        }

        decodeUint32(self) {
            var value = self.view.getUint32(self.offset)
            self.offset += 4
            return value
        }

        decodeUint64(self) {
            // TODO: maybe there's a more clever way to do this.
            throw new TypeError("mpack: javascript doesn't support 64 bits integer")
        }

        decodeInt8(self) {
            var value = self.view.getInt8(self.offset)
            self.offset += 1
            return value
        }

        decodeInt16(self) {
            var value = self.view.getInt16(self.offset)
            self.offset += 2
            return value
        }

        decodeInt32(self) {
            var value = self.view.getInt32(self.offset)
            self.offset += 4
            return value
        }

        decodeInt64(self) {
            // TODO: maybe there's a more clever way to do this.
            throw new TypeError("mpack: javascript doesn't support 64 bits integer")
        }

        decodeFloat32(self) {
            var value = self.view.getFloat32(self.offset)
            self.offset += 4
            return value
        }

        decodeFloat64(self) {
            var value = self.view.getFloat64(self.offset)
            self.offset += 8
            return value
        }

        decodeArrayOfLength(self, length) {
            var array = new Array(length)
            var index = undefined

            for (index = 0; index !== length; ++index) {
                array[index] = this.decodeObject(self)
            }

            return array
        }

        decodeFixarray(self, tag) {
            return this.decodeArrayOfLength(self, tag & ~this.FIXARRAY)
        }

        decodeArray16(self) {
            return this.decodeArrayOfLength(self, this.decodeUint16(self))
        }

        decodeArray32(self) {
            return this.decodeArrayOfLength(self, this.decodeUint32(self))
        }

        decodeMapOfLength(self, length) {
            var map = {}
            var key = undefined
            var val = undefined
            var index = undefined

            for (index = 0; index !== length; ++index) {
                key = this.decodeObject(self)
                val = this.decodeObject(self)
                map[key] = val
            }

            return map
        }

        decodeFixmap(self, tag) {
            return this.decodeMapOfLength(self, tag & ~this.FIXMAP)
        }

        decodeMap16(self) {
            return this.decodeMapOfLength(self, this.decodeUint16(self))
        }

        decodeMap32(self) {
            return this.decodeMapOfLength(self, this.decodeUint32(self))
        }

        decodeExtendedOfLength(self, length) {
            var type = this.decodeUint8(self)
            var offset = self.offset
            self.offset += length
            return new Extended(type, new Uint8Array(self.view.buffer, self.view.byteOffset + offset, length))
        }

        decodeFixext1(self) {
            return this.decodeExtendedOfLength(self, 1)
        }

        decodeFixext2(self) {
            return this.decodeExtendedOfLength(self, 2)
        }

        decodeFixext4(self) {
            return this.decodeExtendedOfLength(self, 4)
        }

        decodeFixext8(self) {
            return this.decodeExtendedOfLength(self, 8)
        }

        decodeFixext16(self) {
            return this.decodeExtendedOfLength(self, 16)
        }

        decodeExt8(self) {
            return this.decodeExtendedOfLength(self, this.decodeUint8(self))
        }

        decodeExt16(self) {
            return this.decodeExtendedOfLength(self, this.decodeUint16(self))
        }

        decodeExt32(self) {
            return this.decodeExtendedOfLength(self, this.decodeUint32(self))
        }

        decodeObject(self) {
            var tag = this.decodeUint8(self)

            if ((tag & 0x80) === this.POSITIVE_FIXNUM) {
                return this.decodePositiveFixnum(self, tag)
            }

            if ((tag & 0xE0) === this.NEGATIVE_FIXNUM) {
                return this.decodeNegativeFixnum(self, tag)
            }

            if ((tag & 0xE0) === this.FIXSTR) {
                return this.decodeFixstr(self, tag)
            }

            if ((tag & 0xF0) === this.FIXARRAY) {
                return this.decodeFixarray(self, tag)
            }

            if ((tag & 0xF0) === this.FIXMAP) {
                return this.decodeFixmap(self, tag)
            }

            switch (tag) {
                case this.NIL:
                    return null

                case this.FALSE:
                    return false

                case this.TRUE:
                    return true

                case this.STR8:
                    return this.decodeStr8(self)

                case this.STR16:
                    return this.decodeStr16(self)

                case this.STR32:
                    return this.decodeStr32(self)

                case this.BIN8:
                    return this.decodeBin8(self)

                case this.BIN16:
                    return this.decodeBin16(self)

                case this.BIN32:
                    return this.decodeBin32(self)

                case this.INT8:
                    return this.decodeInt8(self)

                case this.INT16:
                    return this.decodeInt16(self)

                case this.INT32:
                    return this.decodeInt32(self)

                case this.INT64:
                    return this.decodeInt64(self)

                case this.UINT8:
                    return this.decodeUint8(self)

                case this.UINT16:
                    return this.decodeUint16(self)

                case this.UINT32:
                    return this.decodeUint32(self)

                case this.UINT64:
                    return this.decodeUint64(self)

                case this.FLOAT32:
                    return this.decodeFloat32(self)

                case this.FLOAT64:
                    return this.decodeFloat64(self)

                case this.ARRAY16:
                    return this.decodeArray16(self)

                case this.ARRAY32:
                    return this.decodeArray32(self)

                case this.MAP16:
                    return this.decodeMap16(self)

                case this.MAP32:
                    return this.decodeMap32(self)

                case this.FIXEXT1:
                    return this.decodeFixext1(self)

                case this.FIXEXT2:
                    return this.decodeFixext2(self)

                case this.FIXEXT4:
                    return this.decodeFixext4(self)

                case this.FIXEXT8:
                    return this.decodeFixext8(self)

                case this.FIXEXT16:
                    return this.decodeFixext16(self)

                case this.EXT8:
                    return this.decodeExt8(self)

                case this.EXT16:
                    return this.decodeExt16(self)

                case this.EXT32:
                    return this.decodeExt32(self)

                default:
                    throw new TypeError("mpack: decoder found an unknown tag: " + tag)
            }
        }
    }

    // export class Encoder {
    //     private NIL = 0xc0
    //     private FALSE = 0xc2
    //     private TRUE = 0xc3
    //     private BIN8 = 0xc4
    //     private BIN16 = 0xc5
    //     private BIN32 = 0xc6
    //     private EXT8 = 0xc7
    //     private EXT16 = 0xc8
    //     private EXT32 = 0xc9
    //     private FLOAT32 = 0xca
    //     private FLOAT64 = 0xcb
    //     private UINT8 = 0xcc
    //     private UINT16 = 0xcd
    //     private UINT32 = 0xce
    //     private UINT64 = 0xcf
    //     private INT8 = 0xd0
    //     private INT16 = 0xd1
    //     private INT32 = 0xd2
    //     private INT64 = 0xd3
    //     private FIXEXT1 = 0xd4
    //     private FIXEXT2 = 0xd5
    //     private FIXEXT4 = 0xd6
    //     private FIXEXT8 = 0xd7
    //     private FIXEXT16 = 0xd8
    //     private STR8 = 0xd9
    //     private STR16 = 0xda
    //     private STR32 = 0xdb
    //     private ARRAY16 = 0xdc
    //     private ARRAY32 = 0xdd
    //     private MAP16 = 0xde
    //     private MAP32 = 0xdf
    //     private FIXARRAY = 0x90
    //     private FIXSTR = 0xa0
    //     private FIXMAP = 0x80
    //     private POSITIVE_FIXNUM = 0x00
    //     private NEGATIVE_FIXNUM = 0xe0
    //     buffer;
    //     offset;
    //     view;
    //     length;
    //     constructor(buffer) {
    //         this.view = undefined
    //         this.buffer = undefined
    //         this.length = 0

    //         if (buffer !== undefined) {
    //             if (buffer instanceof ArrayBuffer) {
    //                 this.buffer = buffer
    //             }
    //             else {
    //                 throw new TypeError("mpack.Encoder: buffer must be undefined or an ArrayBuffer instance")
    //             }
    //         }
    //     }

    //     memcpy(dst, src) {
    //         new Uint8Array(dst).set(new Uint8Array(src))
    //         return dst
    //     }

    //     encodeUTF8(s) {
    //         return utf8.encode(s)
    //     }


    //     writeUint8(self, value) {
    //         var view = self.view
    //         var offset = self.length

    //         if ((offset + 1) <= view.byteLength) {
    //             view.setUint8(offset, value)
    //         }

    //         self.length += 1
    //     }

    //     writeUint16(self, value) {
    //         var view = self.view
    //         var offset = self.length

    //         if ((offset + 2) <= view.byteLength) {
    //             view.setUint16(offset, value)
    //         }

    //         self.length += 2
    //     }

    //     writeUint32(self, value) {
    //         var view = self.view
    //         var offset = self.length

    //         if ((offset + 4) <= view.byteLength) {
    //             view.setUint32(offset, value)
    //         }

    //         self.length += 4
    //     }

    //     writeInt8(self, value) {
    //         var view = self.view
    //         var offset = self.length

    //         if ((offset + 1) <= view.byteLength) {
    //             view.setInt8(offset, value)
    //         }

    //         self.length += 1
    //     }

    //     writeInt16(self, value) {
    //         var view = self.view
    //         var offset = self.length

    //         if ((offset + 2) <= view.byteLength) {
    //             view.setInt16(offset, value)
    //         }

    //         self.length += 2
    //     }

    //     writeInt32(self, value) {
    //         var view = self.view
    //         var offset = self.length

    //         if ((offset + 4) <= view.byteLength) {
    //             view.setInt32(offset, value)
    //         }

    //         self.length += 4
    //     }

    //     writeFloat64(self, value) {
    //         var view = self.view
    //         var offset = self.length

    //         if ((offset + 8) <= view.byteLength) {
    //             view.setFloat64(offset, value)
    //         }

    //         self.length += 8
    //     }

    //     writeString(self, string, stringOffset, stringLength) {
    //         var view = self.view
    //         var offset = self.length
    //         var i = undefined
    //         var j = undefined

    //         if ((offset + stringLength) <= view.byteLength) {
    //             for (i = 0, j = offset; i !== stringLength; ++i, ++j) {
    //                 view.setUint8(j, string.charCodeAt(i + stringOffset))
    //             }
    //         }

    //         self.length += stringLength
    //     }

    //     writeBytes(self, bytes, bytesOffset, bytesLength) {
    //         var view = self.view
    //         var offset = self.length

    //         if ((offset + bytesLength) <= view.byteLength) {
    //             new Uint8Array(view.buffer, view.byteOffset + offset).set(bytes)
    //         }

    //         self.length += bytesLength
    //     }

    //     encodeNil(self) {
    //         this.writeUint8(self, this.NIL)
    //     }

    //     encodeFalse(self) {
    //         this.writeUint8(self, this.FALSE)
    //     }

    //     encodeTrue(self) {
    //         this.writeUint8(self, this.TRUE)
    //     }

    //     encodeBoolean(self, object) {
    //         if (object === true) {
    //             return this.encodeTrue(self)
    //         }

    //         if (object === false) {
    //             return this.encodeFalse(self)
    //         }
    //     }

    //     encodeString(self, object) {
    //         var length = undefined

    //         object = this.encodeUTF8(object)
    //         length = object.length

    //         if (length <= 31) {
    //             this.writeUint8(self, this.FIXSTR | length)
    //         }
    //         else if (length <= 255) {
    //             this.writeUint8(self, this.STR8)
    //             this.writeUint8(self, length)
    //         }
    //         else if (length <= 65535) {
    //             this.writeUint8(self, this.STR16)
    //             this.writeUint16(self, length)
    //         }
    //         else {
    //             this.writeUint8(self, this.STR32)
    //             this.writeUint32(self, length)
    //         }

    //         this.writeString(self, object, 0, length)
    //     }

    //     encodeBinary(self, object) {
    //         var length = object.byteLength

    //         if (length <= 255) {
    //             this.writeUint8(self, this.BIN8)
    //             this.writeUint8(self, length)
    //         }
    //         else if (length <= 65535) {
    //             this.writeUint8(self, this.BIN16)
    //             this.writeUint16(self, length)
    //         }
    //         else {
    //             this.writeUint8(self, this.BIN32)
    //             this.writeUint32(self, length)
    //         }

    //         this.writeBytes(self, object, 0, length)
    //     }

    //     encodeInteger(self, object) {
    //         if (object >= 0) {
    //             if (object <= 127) {
    //                 this.writeUint8(self, this.POSITIVE_FIXNUM | object)
    //             }
    //             else if (object <= 255) {
    //                 this.writeUint8(self, this.UINT8)
    //                 this.writeUint8(self, object)
    //             }
    //             else if (object <= 65535) {
    //                 this.writeUint8(self, this.UINT16)
    //                 this.writeUint16(self, object)
    //             }
    //             else if (object <= 4294967295) {
    //                 this.writeUint8(self, this.UINT32)
    //                 this.writeUint32(self, object)
    //             }
    //             else {
    //                 // TODO: maybe there's a more clever way to do this.
    //                 throw new TypeError("mpack: javascript doesn't support 64 bits integer")
    //             }
    //         }

    //         else {
    //             if (object >= -15) {
    //                 this.writeUint8(self, this.NEGATIVE_FIXNUM | object)
    //             }
    //             else if (object >= -128) {
    //                 this.writeUint8(self, this.INT8)
    //                 this.writeInt8(self, object)
    //             }
    //             else if (object >= -32768) {
    //                 this.writeUint8(self, this.INT16)
    //                 this.writeInt16(self, object)
    //             }
    //             else if (object >= -2147483648) {
    //                 this.writeUint8(self, this.INT32)
    //                 this.writeInt32(self, object)
    //             }
    //             else {
    //                 // TODO: maybe there's a more clever way to handle this.
    //                 throw new TypeError("mpack: javascript doesn't support 64 bits integer")
    //             }
    //         }
    //     }

    //     encodeFloat(self, object) {
    //         this.writeUint8(self, this.FLOAT64)
    //         this.writeFloat64(self, object)
    //     }

    //     encodeNumber(self, object) {
    //         if ((object % 1) === 0) {
    //             this.encodeInteger(self, object)
    //         }
    //         else {
    //             this.encodeFloat(self, object)
    //         }
    //     }

    //     encodeArray(self, object) {
    //         var length = object.length
    //         var index = undefined

    //         if (length <= 15) {
    //             this.writeUint8(self, this.FIXARRAY | length)
    //         }
    //         else if (length <= 65535) {
    //             this.writeUint8(self, this.ARRAY16)
    //             this.writeUint16(self, length)
    //         }
    //         else {
    //             this.writeUint8(self, this.ARRAY32)
    //             this.writeUint32(self, length)
    //         }

    //         for (index in object) {
    //             this.encodeObject(self, object[index])
    //         }
    //     }

    //     encodeMap(self, object) {
    //         var length = 0
    //         var key = undefined

    //         for (key in object) {
    //             length += 1
    //         }

    //         if (length <= 15) {
    //             this.writeUint8(self, this.FIXMAP | length)
    //         }
    //         else if (length <= 65535) {
    //             this.writeUint8(self, this.MAP16)
    //             this.writeUint16(self, length)
    //         }
    //         else {
    //             this.writeUint8(self, this.MAP32)
    //             this.writeUint32(self, length)
    //         }

    //         for (key in object) {
    //             this.encodeObject(self, key)
    //             this.encodeObject(self, object[key])
    //         }
    //     }

    //     encodeExtended(self, object) {
    //         var type = object.type
    //         var data = object.data
    //         var length = data.byteLength

    //         switch (length) {
    //             case 1:
    //                 this.writeUint8(self, this.FIXEXT1)
    //                 break

    //             case 2:
    //                 this.writeUint8(self, this.FIXEXT2)
    //                 break

    //             case 4:
    //                 this.writeUint8(self, this.FIXEXT4)
    //                 break

    //             case 8:
    //                 this.writeUint8(self, this.FIXEXT8)
    //                 break

    //             case 16:
    //                 this.writeUint8(self, this.FIXEXT16)
    //                 break

    //             default:
    //                 if (length <= 255) {
    //                     this.writeUint8(self, this.EXT8)
    //                     this.writeUint8(self, length)
    //                 }
    //                 else if (length <= 65535) {
    //                     this.writeUint8(self, this.EXT16)
    //                     this.writeUint16(self, length)
    //                 }
    //                 else {
    //                     this.writeUint8(self, this.EXT32)
    //                     this.writeUint32(self, length)
    //                 }
    //         }

    //         this.writeUint8(self, type)
    //         this.writeBytes(self, data, 0, length)
    //     }

    //     encodeObject(self, object) {
    //         switch (typeof object) {
    //             case 'string':
    //                 return this.encodeString(self, object)

    //             case 'number':
    //                 return this.encodeNumber(self, object)

    //             case 'boolean':
    //                 return this.encodeBoolean(self, object)

    //             case 'object':
    //                 break

    //             default:
    //                 return
    //         }

    //         if (object === null) {
    //             return this.encodeNil(self)
    //         }

    //         if (object instanceof Array) {
    //             return this.encodeArray(self, object)
    //         }

    //         if (object instanceof ArrayBuffer) {
    //             return this.encodeBinary(self, new Uint8Array(object))
    //         }

    //         if (object instanceof Uint8Array) {
    //             return this.encodeBinary(self, object)
    //         }

    //         if (object instanceof Uint16Array) {
    //             return this.encodeBinary(self, new Uint8Array(object))
    //         }

    //         if (object instanceof Uint32Array) {
    //             return this.encodeBinary(self, new Uint8Array(object))
    //         }

    //         if (object instanceof Int8Array) {
    //             return this.encodeBinary(self, new Uint8Array(object))
    //         }

    //         if (object instanceof Int16Array) {
    //             return this.encodeBinary(self, new Uint8Array(object))
    //         }

    //         if (object instanceof Int32Array) {
    //             return this.encodeBinary(self, new Uint8Array(object))
    //         }

    //         if (object instanceof DataView) {
    //             return this.encodeBinary(self, new Uint8Array(object));
    //         }

    //         if (object instanceof Extended) {
    //             return this.encodeExtended(self, object)
    //         }

    //         if ((typeof object) === 'object') {
    //             return this.encodeMap(self, object)
    //         }

    //         throw new TypeError("mpack: no encoding available for objects of type " + typeof (object))
    //     }

    //     encodeValue(self, object, callback) {
    //         var oldLength = self.length
    //         var newLength = undefined

    //         if (self.buffer === undefined) {
    //             self.buffer = new ArrayBuffer(1000)
    //             self.view = new DataView(self.buffer)
    //         }

    //         callback(self, object)
    //         newLength = self.length

    //         if (newLength > self.view.byteLength) {
    //             newLength = (Math.ceil(newLength / 1000) + ((newLength % 1000) === 0 ? 0 : 1)) * 1000
    //             self.buffer = this.memcpy(new ArrayBuffer(newLength), self.buffer)
    //             self.view = new DataView(self.buffer)
    //             self.length = oldLength
    //             callback(self, object)
    //         }

    //         return self
    //     }

    //     encode(object) {
    //         return this.encodeValue(this, object, this.encodeObject)
    //     }

    //     encodeNil(object) {
    //         return this.encodeValue(this, null, this.encodeNil);
    //     }

    //     encodeBoolean(object) {
    //         return encodeValue(this, null, object ? encodeTrue : encodeFalse)
    //     }

    //     encodeNumber(object) {
    //         return encodeValue(this, object, encodeNumber)
    //     }

    //     encodeInteger(object) {
    //         return encodeValue(this, object, encodeInteger)
    //     }

    //     encodeFloat(object) {
    //         return encodeValue(this, object, encodeFloat)
    //     }

    //     encodeString(object) {
    //         return encodeValue(this, object, encodeString)
    //     }

    //     encodeBinary(object) {
    //         return encodeValue(this, object, encodeBinary)
    //     }

    //     encodeArray(object) {
    //         return encodeValue(this, object, encodeArray)
    //     }

    //     encodeMap(object) {
    //         return encodeValue(this, object, encodeMap)
    //     }

    //     encodeExtended(object) {
    //         return encodeValue(this, object, encodeExtended)
    //     }

    //     bytes() {
    //         return (this.buffer === undefined) ? new Uint8Array() : new Uint8Array(this.buffer, 0, this.length)
    //     }

    //     clear() {
    //         this.buffer = undefined
    //         this.length = 0
    //     }

    //     flush() {
    //         var bytes = this.bytes()
    //         this.clear()
    //         return bytes
    //     }

    // }
}